using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;

using Microsoft.AspNetCore.Authorization;

using OpenIddict.Abstractions;

using Volo.Abp;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.OpenIddict.Scopes;

using WL.Think.Abp.OpenIddict.Dtos;
using WL.Think.Abp.OpenIddict.Permission;
using WL.Think.Abp.Services;

namespace WL.Think.Abp.OpenIddict.Services;

[Authorize(OpenIddictPermissions.Scopes.Default)]
public class ScopeAppService : ThinkAppService<OpenIddictScope,ScopeDto, ScopePagedDto, ScopeCreateUpdateDto>, IScopeAppService
{
    private readonly AbpScopeManager _manager;
    protected override string? CreatePolicyName => OpenIddictPermissions.Scopes.Create;
    protected override string? UpdatePolicyName => OpenIddictPermissions.Scopes.Update;
    protected override string? DeletePolicyName => OpenIddictPermissions.Scopes.Delete;

    public ScopeAppService(AbpScopeManager manager, IRepository<OpenIddictScope, Guid> repository):base(repository)
    {
        _manager = manager;
    }

    public override async Task<ScopeDto> CreateAsync(ScopeCreateUpdateDto input)
    {
        await CheckCreatePolicyAsync();
        if(await Repository.AnyAsync(x => x.Name == input.Name))
        {
            throw new UserFriendlyException(L["Input Name is Ready"]);
        }
        var descriptor = new OpenIddictScopeDescriptor
        {
            Name = input.Name,
            DisplayName = input.DisplayName,
            Description = input.Description
        };
        descriptor.Resources.Add(input.Name!);
        foreach (var resource in input.Resources)
        {
            descriptor.Resources.Add(resource);
        }
        var m =await _manager.CreateAsync(descriptor);
        return await MapToGetOutputDtoAsync(m.ToEntity());
    }

    public override async Task<ScopeDto> UpdateAsync(Guid id, ScopeCreateUpdateDto input)
    {
        await CheckUpdatePolicyAsync();
        var item = await Repository.GetAsync(id);
        if (item.Name != input.Name && await Repository.AnyAsync(x => x.Name == input.Name))
        {
            throw new UserFriendlyException(L["Input Name is Ready"]);
        }
        item.Name = input.Name;
        item.DisplayName = input.DisplayName;
        item.Description = input.Description;
        input.Resources.Add(input.Name!);
        item.Resources = WriteStream(writer =>
        {
            writer.WriteStartArray();
            foreach (var resource in input.Resources)
            {
                writer.WriteStringValue(resource);
            }
            writer.WriteEndArray();
        });
        await _manager.UpdateAsync(item.ToModel());
        return await MapToGetOutputDtoAsync(item);
    }

    public override async Task DeleteAsync(Guid id)
    {
        await CheckDeletePolicyAsync();
        var item = await Repository.GetAsync(id);
        var model = item.ToModel();
        await _manager.DeleteAsync(model);
    }

    protected override async Task<IQueryable<OpenIddictScope>> CreateFilteredQueryAsync(ScopePagedDto input)
    {
        var queryable = await base.CreateFilteredQueryAsync(input);
        queryable = queryable.WhereIf(!string.IsNullOrWhiteSpace(input.Filter), x => x.Name.Contains(input.Filter!) || x.DisplayName.Contains(input.Filter!) || x.Description.Contains(input.Filter!));
        return queryable;
    }

    protected virtual string WriteStream(Action<Utf8JsonWriter> action)
    {
        using (var stream = new MemoryStream())
        {
            using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
            {
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                Indented = false
            }))
            {
                action(writer);
                writer.Flush();
                return Encoding.UTF8.GetString(stream.ToArray());
            }
        }
    }
}
